/**
 * 事件派发器
 */
class Handler{
    public caller:any;
    public callback:any;
    public args:any;
    public once:boolean = false;
    public constructor(caller:any,callback:()=>void,once = false,args?:any){
        this.setTo(caller,callback,once,args)
    }

    public setTo(caller:any,callback:()=>void,once:boolean,args:any = null){
        this.caller = caller;
        this.callback = callback;
        this.once = once;
        this.args = args;
        return this;
    }

    public run(...args:any[]){
        if(args.length == 0){
            this.callback.call(this.caller,this.args);
        } else {
            this.callback.call(this.caller,...args,this.args)
        }
        if(this.once){
            this.clear();
        }
    }
    public clear(){
        this.caller = null;
        this.callback = null;
        this.args = null;
        this.once = false;
        if(!Handler.isMax()){
            Handler.pool.push(this);
        }
    }
    public static isMax(){
        return this.pool.length == this.maxSize;
    }
    public static pool:Handler[] = [];
    public static maxSize:number = 100;
    public static create(caller:any,callback:()=>void,once = false,args?:any):Handler{
        if(this.pool.length){
            return (this.pool.pop())!.setTo(caller,callback,once,args);
        }
        return new Handler(caller,callback,once,args)
    }
}
export default class EventDispatcher{
    private _events:{[key:string]:Handler[]|Handler} = {}
    public on(name:string,callback:()=>void,caller?:any,args?:any){
        if(!this._events[name]){
            this._events[name] = Handler.create(caller,callback,false,args);
            return;
        }
        if(Array.isArray(this._events[name])){
            (<Handler[]>this._events[name]).push(Handler.create(caller,callback,false,args));
            return;
        }
        this._events[name] = [this._events[name] as Handler,Handler.create(caller,callback,false,args)]
    }
    public off(name:string,callback:()=>void,caller?:any){
        if(!this._events[name]){
            return;
        }
        if(!Array.isArray(this._events[name])){
            delete this._events[name];
            (<Handler>this._events[name]).clear();
            return;
        }
        for(let i = 0;i<(<Handler[]>this._events[name]).length;i++){
            if((<Handler[]>this._events[name])[i].callback == callback){
                if(!caller){
                    (<Handler[]>this._events[name]).splice(i,1)[0].clear();
                    return;
                } else if(caller == (<Handler[]>this._events[name])[i].caller){
                    (<Handler[]>this._events[name]).splice(i,1)[0].clear();
                    return;
                }
            }
        }
    }
    public has(name:string,callback:()=>void,caller?:any):boolean{
        if(!this._events[name]){
            return false;
        }
        if(!Array.isArray(this._events[name])){
            if(caller){
                return (<Handler>this._events[name]).callback == callback && caller == (<Handler>this._events[name]).caller
            } 
            return (<Handler>this._events[name]).callback == callback
        }
        for(let i = 0;i<(<Handler[]>this._events[name]).length;i++){
            if((<Handler[]>this._events[name])[i].callback == callback){
                if(caller){
                    if(caller == (<Handler[]>this._events[name])[i].caller){
                        return true;
                    }
                } else {
                    return true;
                }
            }
        }
        return false;
    }
    public dispatch(name:string,args?:any){
        if(!this._events[name]){
            return false;
        }
        let item:Handler;
        if(!Array.isArray(this._events[name])){
            item = this._events[name] as Handler;
            if((<Handler>this._events[name]).once){
                delete this._events[name];
            }
            return item.run(args);
        }
        for(let i = 0;i<(<Handler[]>this._events[name]).length;i++){
            item = (<Handler[]>this._events[name])[i];
            if(item.once){
                (<Handler[]>this._events[name]).splice(i,1);
                i--;
            }
            item.run(args);
        }
        if(!(<Handler[]>this._events[name]).length){
            delete this._events[name];
        }
    }
    public offByName(name:string){
        if(!this._events[name]){
            return;
        }
        if(!Array.isArray(this._events[name])){
            (<Handler>this._events[name]).clear();
            delete this._events[name];
            return;
        }
        while((<Handler[]>this._events[name]).length){
            (<Handler[]>this._events[name]).pop()?.clear()
        }
    }
    public offAll(){
        for(let key in this._events){
            this.offByName(key)
            delete this._events[key]
        }
    }
}